package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"

	"log"
)

var (
	BATCH_CONFIG_FILE string = "./batch_config.json"
)

type MDGetInfo struct {
	MdName     string
	OutPath    string
	ImagePath  string
	ImgOutPath string
	Rename     string
}

type MDGetInfo2 struct {
	mdName         string
	newName        string
	outPath        string
	imgInPath      string
	imgOutPath     string
	imgInBasePath  string
	imgOutBasePath string
}

type MDGetInfos []MDGetInfo
type MDGetInfos2 []MDGetInfo2

type MDGetFlag struct {
	MDGetInfo   `json:"-"`
	Batch       bool
	Copy        bool
	ReplacePath bool
	All         bool
}

type MDGet struct {
	Info    MDGetInfos
	outInfo MDGetInfos2
	Flag    MDGetFlag
}

var mdGets MDGet

func main() {
	log.Println("mdget app start")

	mdGets.Flag.register_flag()
	if !mdGets.Flag.flag_check() {
		flag.Usage()
		os.Exit(1)
	}

	mdGets.handle()
	log.Println("----------------------Success.-----------------------")
}

func (mgf *MDGetFlag) register_flag() {
	flag.StringVar(&mgf.MdName, "f", "", "Specify the name of the markdown document to be exported.")
	flag.StringVar(&mgf.OutPath, "o", "", "Specify the path of the markdown document to exported")
	flag.StringVar(&mgf.ImagePath, "i", "images/markdown", "Specify the path of the markdown images")
	flag.StringVar(&mgf.ImgOutPath, "io", "", "Modify the path of the markdown images")
	flag.BoolVar(&mgf.Batch, "b", false, "Generate batch config json file and rerun to use it with -b")
	flag.BoolVar(&mgf.Copy, "c", true, "get markdown by copy")
	flag.BoolVar(&mgf.All, "a", false, "get all markdown")
	flag.StringVar(&mgf.Rename, "r", "", "rename the markdown")
	flag.Parse()
}

func (mgf *MDGetFlag) flag_check() bool {
	if mgf.All {
		return true
	}
	if mgf.Batch {
		return true
	}
	if mgf.MdName != "" {
		return true
	}
	return false
}

func (mg *MDGet) info_convert() {
	mg.outInfo = make([]MDGetInfo2, len(mg.Info))

	for i, _ := range mg.Info {
		mg.Info[i].ImagePath = path.Clean(mg.Info[i].ImagePath)

		basePath, name := filepath.Split(mg.Info[i].MdName)
		baseName := strings.Split(name, ".")[0]

		/* 重命名只能在处理单个文件时有效 */
		if mg.Flag.All || mg.Flag.Batch || mg.Info[i].Rename == "" {
			mg.outInfo[i].newName = mg.Info[i].MdName
		} else {
			mg.outInfo[i].newName = mg.Info[i].Rename
		}

		_, newName := filepath.Split(mg.outInfo[i].newName)
		newBaseName := strings.Split(newName, ".")[0]

		mg.outInfo[i].mdName = mg.Info[i].MdName
		mg.outInfo[i].imgInPath = path.Join(basePath, mg.Info[i].ImagePath, baseName)

		if mg.Info[i].OutPath == "" {
			mg.outInfo[i].outPath = path.Join(basePath, "mdget_"+baseName)
			log.Println("outpath: ", mg.outInfo[i].outPath)
		} else {
			mg.outInfo[i].outPath = mg.Info[i].OutPath
		}

		mg.outInfo[i].imgInBasePath = mg.Info[i].ImagePath
		mg.outInfo[i].imgOutBasePath = mg.Info[i].ImgOutPath
		if mg.Info[i].ImgOutPath == "" {
			mg.Flag.ReplacePath = false
			mg.outInfo[i].imgOutPath = path.Join(mg.outInfo[i].outPath, mg.Info[i].ImagePath, newBaseName)
		} else {
			mg.Flag.ReplacePath = true
			mg.outInfo[i].imgOutPath = path.Join(mg.outInfo[i].outPath, mg.Info[i].ImgOutPath, newBaseName)
		}
		log.Println("test", mg.outInfo[i].imgOutPath)
	}
}

func FileReplaceStr(file, oldStr, newStr string) error {
	var err error
	data, err := os.ReadFile(file)
	if err != nil {
		return err
	}

	var old, new string
	old = strings.Replace(strings.ReplaceAll(oldStr, "\\", "/"), "./", "", 1)
	if []byte(old)[len(old)-1] == '/' {
		old = string([]byte(old)[:len(old)-1])
	}
	new = strings.Replace(strings.ReplaceAll(newStr, "\\", "/"), "./", "", 1)
	if []byte(new)[len(new)-1] == '/' {
		new = string([]byte(new)[:len(new)-1])
	}

	fileStr := string(data)
	fileStr = strings.ReplaceAll(fileStr, old, new)
	err = os.WriteFile(file, []byte(fileStr), 0777)
	return err
}

func (mg *MDGet) handle() {
	if mg.Flag.All {
		mg.handle_all()
	} else {
		if mg.Flag.Batch {
			mg.handle_batch()
		} else {
			mg.handle_single()
		}
	}
}

func (mg *MDGet) handle_all() {
	files, err := os.ReadDir("./")
	if err != nil {
		log.Fatal(err)
	}

	var fList []string = make([]string, 0)
	for _, file := range files {
		if !file.IsDir() && filepath.Ext(file.Name()) == ".md" {
			fList = append(fList, file.Name())
		}
	}

	log.Println("files total: ", len(fList))

	var allInfo MDGet
	allInfo.Flag = mg.Flag
	allInfo.Info = make(MDGetInfos, len(fList))
	if mg.Flag.ImgOutPath == "" {
		mg.Flag.ImgOutPath = mg.Flag.ImagePath
	}
	for i, _ := range allInfo.Info {
		allInfo.Info[i].MdName = fList[i]
		allInfo.Info[i].ImagePath = mg.Flag.ImagePath
		allInfo.Info[i].ImgOutPath = mg.Flag.ImgOutPath
		allInfo.Info[i].OutPath = mg.Flag.OutPath
	}

	allInfo.info_convert()
	for i, _ := range allInfo.outInfo {
		err := allInfo.get_md(&allInfo.outInfo[i])
		if err != nil {
			allInfo.remove_target()
			log.Fatal(err)
		}
	}

	if allInfo.Flag.Copy {
		log.Println("copy mode")
	} else {
		log.Println("cut mode")
		allInfo.remove_source()
	}
}

func (mg *MDGet) handle_batch() {
	if !isFileOrPathExists(BATCH_CONFIG_FILE) {
		var batchConf = MDGet{
			Info: MDGetInfos{
				MDGetInfo{"test.md", "output", "images/markdown", "images/MARKDOWN", ""},
				MDGetInfo{"test1.md", "output", "images/markdown", "images/MARKDOWN", ""},
			},
			Flag: MDGetFlag{Batch: true, Copy: true, ReplacePath: false},
		}
		bytes, err := json.MarshalIndent(batchConf, "", "	")
		if err != nil {
			log.Fatal(err)
		}
		err = os.WriteFile(BATCH_CONFIG_FILE, bytes, 0777)
		if err != nil {
			log.Fatal(err)
		}
		log.Println("please modify", BATCH_CONFIG_FILE, "file to customize")
		return
	}

	bytes, err := os.ReadFile(BATCH_CONFIG_FILE)
	if err != nil {
		log.Fatal(err)
	}

	var batchConf MDGet
	err = json.Unmarshal(bytes, &batchConf)
	if err != nil {
		log.Fatal(err)
	}

	batchConf.info_convert()
	for i, _ := range batchConf.outInfo {
		err := batchConf.get_md(&batchConf.outInfo[i])
		if err != nil {
			batchConf.remove_target()
			log.Fatal(err)
		}
	}

	if batchConf.Flag.Copy {
		log.Println("copy mode")
	} else {
		log.Println("cut mode")
		batchConf.remove_source()
	}
}

func (mg *MDGet) handle_single() {
	mg.Info = append(mg.Info, mg.Flag.MDGetInfo)

	mg.info_convert()

	var outInfo MDGetInfo2 = mg.outInfo[0]

	err := mg.get_md(&outInfo)
	if err != nil {
		mg.remove_target()
		log.Fatal(err)
	}

	if mg.Flag.Copy {
		log.Println("copy mode")
	} else {
		log.Println("cut mode")
		mg.remove_source()
	}
}

func (mg *MDGet) get_md(outInfo *MDGetInfo2) error {
	log.Println("----------------------" + outInfo.mdName + "----------------------")
	defer log.Println(("------------------------------------------------------------------"))
	err := os.MkdirAll(outInfo.outPath, os.ModePerm)
	log.Println("out path: ", outInfo.outPath)
	if err != nil {
		return err
	}

	_, err = copy_file(outInfo.mdName, filepath.Join(outInfo.outPath, outInfo.newName))
	if err != nil {
		return err
	}
	log.Println("copy ", outInfo.newName, " success")

	if mg.Flag.ReplacePath {
		err = FileReplaceStr(filepath.Join(outInfo.outPath, outInfo.newName), "]("+outInfo.imgInBasePath, "]("+outInfo.imgOutBasePath)
		if err != nil {
			return err
		}

		err = FileReplaceStr(filepath.Join(outInfo.outPath, outInfo.newName), "src=\""+outInfo.imgInBasePath, "src=\""+outInfo.imgOutBasePath)
		if err != nil {
			return err
		}
	}

	if !mg.Flag.All || !mg.Flag.Batch || mg.Flag.Rename != "" {
		old := GetFileBaseName(outInfo.mdName)
		new := GetFileBaseName(outInfo.newName)
		err = FileReplaceStr(filepath.Join(outInfo.outPath, outInfo.newName), old, new)
		if err != nil {
			return err
		}
	}

	imgList, err := ioutil.ReadDir(outInfo.imgInPath)
	if err != nil {
		log.Println("not found image file")
		return nil
	}

	if len(imgList) > 0 {
		log.Println(outInfo.imgOutPath)
		err = os.MkdirAll(outInfo.imgOutPath, os.ModePerm)
		log.Println("imgae out path: ", outInfo.imgOutPath)
		if err != nil {
			return err
		}
	} else {
		return nil
	}

	for _, img := range imgList {
		var in, out string = filepath.Join(outInfo.imgInPath, img.Name()), filepath.Join(outInfo.imgOutPath, img.Name())
		_, err := copy_file(in, out)
		if err != nil {
			return err
		}
		log.Println("copy ", out, " success")
	}
	return nil
}

func (mg *MDGet) remove_source() {
	for _, info := range mg.outInfo {
		os.Remove(info.mdName)

		imgList, _ := ioutil.ReadDir(info.imgInPath)
		for _, img := range imgList {
			os.Remove(filepath.Join(info.imgInPath, img.Name()))
		}
	}
	log.Println("remove all source file")
}

func (mg *MDGet) remove_target() {
	for _, info := range mg.outInfo {
		/* 防止输出到当前路径下发生覆盖 */
		if path.Clean(info.outPath) == path.Clean("./") && info.newName != info.mdName {
			os.Remove(filepath.Join(info.outPath, info.newName))
		}

		imgList, _ := ioutil.ReadDir(info.imgOutPath)
		for _, img := range imgList {
			os.Remove(filepath.Join(info.imgOutPath, img.Name()))
		}
	}
	log.Println("remove all target file")
}

func copy_file(src, dst string) (int64, error) {
	srcPath, _ := filepath.Abs(src)
	dstPath, _ := filepath.Abs(dst)
	if srcPath == dstPath {
		return 0, errors.New("can't overwrite it self")
	}

	sourceFileStat, err := os.Stat(src)
	if err != nil {
		return 0, err
	}
	if !sourceFileStat.Mode().IsRegular() {
		return 0, fmt.Errorf("%s is not a regular file", src)
	}

	source, err := os.Open(src)
	if err != nil {
		return 0, err
	}
	defer source.Close()

	destination, err := os.Create(dst)
	if err != nil {
		return 0, err
	}
	defer destination.Close()

	nBytes, err := io.Copy(destination, source)
	return nBytes, err
}

func isFileOrPathExists(fp string) bool {
	_, err := os.Stat(fp)
	return err == nil
}

func GetFileBaseName(path string) string {
	_, old := filepath.Split(path)
	return strings.Split(old, ".")[0]

}
